package com.thrift.distributed.aspect;

import com.alibaba.fastjson.JSONObject;
import com.thrift.distributed.annotation.ChargeManagement;
import com.thrift.distributed.constant.DistributedEnum;
import com.thrift.distributed.process.ClientConnectPool;
import com.thrift.distributed.process.ClientListener;
import com.thrift.distributed.process.ServiceInfo;
import com.thrift.distributed.service.DistributedService;
import com.thrift.distributed.vo.DistributedLogVo;
import com.thrift.distributed.vo.DistributedResultVo;
import com.thrift.distributed.vo.DistributedVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.thrift.protocol.TProtocol;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;

/**
 * @包名称 com.thrift.distributed.aspect
 * @类描述 <pre></pre>
 * @作者 zhangbo
 * @创建时间 2019/9/19
 * @版本 1.00
 * @修改记录 <pre>
 *     版本           修改人         修改日期         修改内容描述
 *     -----------------------------------------------------------
 *     1.00           Lenovo        2019/9/19
 *     -----------------------------------------------------------
 * </pre>
 */
@Slf4j
@Aspect
@Order(4)
@Configuration
public class ChargeAspect {

    @Autowired
    private ClientConnectPool clientConnectPool;

    @Pointcut("@annotation(com.thrift.distributed.annotation.ChargeManagement)")
    public void annotationChargeManagement() {
    }


    @Around("annotationChargeManagement()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        /*取得注解及注解的属性，校验必传参数*/
        ChargeManagement chargeManagement = resolveMethod(point).getAnnotation(ChargeManagement.class);
        String appIdParamName = chargeManagement.appId();
        int methodType = chargeManagement.methodType();
        String methodName = chargeManagement.methodName();
        boolean failureStrategy = chargeManagement.failureStrategy();
        String[] valueKeys = chargeManagement.valueKeys();
        String paramName = chargeManagement.paramName();
        if (StringUtils.isBlank(methodName)) {
            throw new RuntimeException("methodName未配置");
        }
        if (valueKeys == null || valueKeys.length == 0) {
            throw new RuntimeException("valueKeys未配置");
        }
        Object[] args = point.getArgs();
        if (args.length == 0) {
            throw new RuntimeException("该方法没有入参不能配置收费条件");
        }
        /*获取到读取参数的对象*/
        Object paramObject = null;
        if (StringUtils.isNotBlank(paramName)) {
            String[] paramNames = ((MethodSignature) point.getSignature()).getParameterNames();
            for (int i = 0; i < paramNames.length; ++i) {
                if (paramNames[i].equals(paramName)) {
                    paramObject = args[i];
                }
            }
        } else {
            /*未配置取第一个参数作为入参对象*/
            paramObject = args[0];
        }
        /*获取appid*/
        String getMethodName = "get" + appIdParamName.substring(0, 1).toUpperCase() + appIdParamName.substring(1);
        Method method = paramObject.getClass().getMethod(getMethodName);
        String appId = (String) method.invoke(paramObject, null);
        /*获取methodName，若为0则直接从注解中获取，若为1则需要反射从方法中获取*/
        if (1 == methodType) {
            //从注解中获取
            getMethodName = "get" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
            method = paramObject.getClass().getMethod(getMethodName);
            methodName = (String) method.invoke(paramObject, null);
        }
        /*获取查询条件参数*/
        StringBuffer valueBuffer = new StringBuffer();
        Object value = null;
        for (String valueKey : valueKeys) {
            getMethodName = "get" + valueKey.substring(0, 1).toUpperCase() + valueKey.substring(1);
            method = paramObject.getClass().getMethod(getMethodName);
            value = method.invoke(paramObject, null);
            if (null == value) {
                continue;
            }
            valueBuffer.append(String.valueOf(value));
        }
        ServiceInfo serviceInfo = ClientListener.roundServiceInfo();
        if (serviceInfo == null && !failureStrategy) {
            /*未取得服务配置信息，并且为强校验则拒绝访问*/
            throw new RuntimeException("检测服务未启动，拒绝访问");
        }
        Object result = null;
        if (null != serviceInfo) {
            TProtocol protocol = null;
            try {
                protocol = clientConnectPool.getProtocol(serviceInfo);
                if (null == protocol) {
                /*未能获取到protocol，遍历获取*/
                    Map<String, ServiceInfo> serviceInfoMap = ClientListener.getServiceInfoMap();
                    Iterator<String> iterator = serviceInfoMap.keySet().iterator();
                    while (iterator.hasNext()) {
                        serviceInfo = serviceInfoMap.get(iterator.next());
                        protocol = clientConnectPool.getProtocol(serviceInfo);
                        if (null != protocol) {
                            break;
                        }
                    }
                    if (null == protocol) {
                        throw new RuntimeException("未能获取到服务通讯protocal");
                    }
                }
                DistributedService.Client client = new DistributedService.Client(protocol);
                DistributedVo distributedVo = new DistributedVo();
                distributedVo.setAppId(appId)
                        .setInterfaceName(methodName)
                        .setParamValue(valueBuffer.toString());
                DistributedResultVo distributedResultVo = client.check(distributedVo);
                if (DistributedEnum.AUTH_RESULT_ENUM.ACCESS_DENIED.getCode().equals(distributedResultVo.getState())) {
                /*拒绝访问*/
                    throw new RuntimeException("服务拒绝访问");
                } else if (DistributedEnum.AUTH_RESULT_ENUM.AUTHORIZED_EXPIRATION.getCode().equals(distributedResultVo.getState())) {
                /*授权到期或未生效*/
                    throw new RuntimeException("服务授权到期或未生效");
                } else if (DistributedEnum.AUTH_RESULT_ENUM.INSUFFICIENT_FREQUENCY.getCode().equals(distributedResultVo.getState())) {
                /*使用次数不足*/
                    throw new RuntimeException("接口允许访问次数不足");
                } else if (DistributedEnum.AUTH_RESULT_ENUM.ACCESS_ALLOW.getCode().equals(distributedResultVo.getState()) ||
                        DistributedEnum.AUTH_RESULT_ENUM.ACCESS_FRE_ALLOW.getCode().equals(distributedResultVo.getState())) {
                /*另外两种场景为允许访问*/
                    try {
                        result = point.proceed();
                    } catch (Exception e) {
                        log.error("服务异常，接口使用次数回退：{}", e);
                        if(DistributedEnum.AUTH_RESULT_ENUM.ACCESS_ALLOW.getCode().equals(distributedResultVo.getState())) {
                        /*只有扣除过次数时，才回退*/
                            client.returnCount(distributedVo);
                        }
                        throw e;
                    }
                }
                DistributedLogVo distributedLogVo = new DistributedLogVo();
                distributedLogVo.setAppId(distributedResultVo.getAppId())
                        .setInterfaceName(distributedResultVo.getInterfaceName())
                        .setParamValue(JSONObject.toJSONString(args))
                        .setResultValue(result == null ? "" : JSONObject.toJSONString(result))
                        .setResetRule(distributedResultVo.getResetRule())
                        .setFreeCycle(distributedResultVo.getFreeCycle());
                client.sendLog(distributedLogVo);
            } finally {
                clientConnectPool.returnProtocol(serviceInfo,protocol);
            }
        } else {
            /*弱校验模式，服务异常时可访问*/
            result = point.proceed();
        }
        return result;
    }


    public static Method resolveMethod(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class<?> targetClass = joinPoint.getTarget().getClass();
        Method method = getDeclaredMethodFor(targetClass, signature.getName(), signature.getMethod().getParameterTypes());
        if (null != method) {
            return method;
        }
        throw new IllegalStateException("Cannot resolve target method: " + signature.getMethod().getName());
    }

    public static Method getDeclaredMethodFor(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            /*当前类没取到，则取父类的*/
            Class<?> superClass = clazz.getSuperclass();
            if (null != superClass) {
                return getDeclaredMethodFor(superClass, name, parameterTypes);
            }
        }
        return null;
    }
}
